// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: hyuan

#pragma once

#include <list>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/time.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/nonce_manager.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class CoincheckBaseAuth {
 public:
  using HeaderSet = std::vector<std::pair<std::string, std::string>>;

  CoincheckBaseAuth() {
    header_set_.push_back(std::make_pair("ACCESS-KEY", ""));
    header_set_.push_back(std::make_pair("ACCESS-NONCE", ""));
    header_set_.push_back(std::make_pair("ACCESS-SIGNATURE", ""));
    header_set_.push_back(std::make_pair("Content-type", "application/json"));
    header_set_.push_back(std::make_pair("Cache-Control", "no-cache"));
  }

  ~CoincheckBaseAuth() = default;
  CoincheckBaseAuth(const CoincheckBaseAuth&) = delete;
  CoincheckBaseAuth& operator=(const CoincheckBaseAuth&) = delete;

  std::string GetAuth(
      const std::string& nonce,
      const std::string& url,
      const std::string& query_str,
      const std::string& secret_key) const {
    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(nonce + url + query_str, secret_key);
  }

  const HeaderSet&
  GetHeaderSet(const std::string& host, const std::string& address, const std::string& query_str) {
    CHECK_THROW(!header_set_.empty()) << "Header set is empty";
    auto nonce = std::to_string(impl::NonceManager::Get());
    header_set_[0].second = GetAccessKey();
    header_set_[1].second = nonce;
    header_set_[2].second = GetAuth(nonce, "https://" + host + address, query_str, GetSecretKey());
    return header_set_;
  }

  inline auto GetAccessKey() const -> const std::string& { return access_key_; }
  inline void SetAccessKey(const std::string& access_key) {
    access_key_ = access_key;
    CHECK_THROW(!access_key_.empty()) << "[Coincheck] access key is empty";
  }
  inline auto GetSecretKey() const -> const std::string& { return secret_key_; }
  inline void SetSecretKey(const std::string& secret_key) {
    secret_key_ = secret_key;
    CHECK_THROW(!secret_key_.empty()) << "[Coincheck] secret key is empty";
  }

 private:
  AuthKey key_;
  std::string access_key_;
  std::string secret_key_;
  HeaderSet header_set_;
};

class CoincheckRestAuth : public CoincheckBaseAuth, public BaseRestAuth {
 public:
  explicit CoincheckRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
    SetAccessKey(key_.access_key());
    SetSecretKey(key_.secret_key());
  }
  explicit CoincheckRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)),
        host_(host),
        port_(std::to_string(port)),
        bind_ip_(bind_ip) {
    SetAccessKey(key_.access_key());
    SetSecretKey(key_.secret_key());
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) {
    std::string query_string = builder->ComputeQueryString();
    std::string target = address;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }
    auto ahc = AsioHttpContext::NewGet(
        host_,
        port_,
        bind_ip_,
        target,
        GetHeaderSet(host_, address, query_string.size() > 0 ? "?" + query_string : ""));
    ahc->keep_user_agent = true;

    return ahc;
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    std::string target = address;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }
    auto ahc = AsioHttpContext::NewPost(
        host_,
        port_,
        bind_ip_,
        target,
        GetHeaderSet(host_, address, post_body),
        post_body);
    ahc->keep_user_agent = true;

    return ahc;
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) {
    std::string query_string = builder->ComputeQueryString();
    std::string target = address;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewDelete(
        host_,
        port_,
        bind_ip_,
        target,
        GetHeaderSet(host_, address, ""),
        "");
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
};
